﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StringFast
{
	///<summary>Immutable string. Generated at last moment, only if needed</summary>
	private string m_stringGenerated = "";
	///<summary>Is m_stringGenerated is up to date ?</summary>
	private bool m_isStringGenerated = false;

	///<summary>Working mutable string</summary>
	private char[] m_buffer = null;
	private int m_bufferPos = 0;
	private int m_charsCapacity = 0;

	///<summary>Temporary string used for the Replace method</summary>
	private List<char> m_replacement = null;

	private object m_valueControl = null;
	private int m_valueControlInt = int.MinValue;

	// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

	public StringFast( int initialCapacity = 32 )
	{
		m_buffer = new char[ m_charsCapacity = initialCapacity ];
	}

	public bool IsEmpty()
	{
		return (m_isStringGenerated ? (m_stringGenerated == null) : (m_bufferPos == 0));
	}

	///<summary>Return the string</summary>
	public override string ToString()
	{
		if( !m_isStringGenerated ) // Regenerate the immutable string if needed
		{
			m_stringGenerated = new string( m_buffer, 0, m_bufferPos );

			m_isStringGenerated = true;
		}
		return m_stringGenerated;
	}

	// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
	// Value controls methods: use a value to check if the string has to be regenerated.

	///<summary>Return true if the valueControl has changed (and update it)</summary>
	public bool IsModified( int newControlValue )
	{
		bool changed = (newControlValue != m_valueControlInt);
		if( changed )
			m_valueControlInt = newControlValue;
		return changed;
	}

	///<summary>Return true if the valueControl has changed (and update it)</summary>
	public bool IsModified( object newControlValue )
	{
		bool changed = !(newControlValue.Equals( m_valueControl ));
		if( changed )
			m_valueControl = newControlValue;
		return changed;
	}


	// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
	// Set methods:

	///<summary>Set a string, no memorry allocation</summary>
	public void Set( string str )
	{
		// We fill the m_chars list to manage future appends, but we also directly set the final stringGenerated
		Clear();
		Append( str );
		m_stringGenerated = str;
		m_isStringGenerated = true;
	}
	///<summary>Caution, allocate some memory</summary>
	public void Set( object str )
	{
		Set( str.ToString() );
	}

	///<summary>Append several params: no memory allocation unless params are of object type</summary>
	public void Set<T1, T2>( T1 str1, T2 str2 )
	{
		Clear();
		Append( str1 ); Append( str2 );
	}
	public void Set<T1, T2, T3>( T1 str1, T2 str2, T3 str3 )
	{
		Clear();
		Append( str1 ); Append( str2 ); Append( str3 );
	}
	public void Set<T1, T2, T3, T4>( T1 str1, T2 str2, T3 str3, T4 str4 )
	{
		Clear();
		Append( str1 ); Append( str2 ); Append( str3 ); Append( str4 );
	}
	///<summary>Allocate a little memory (20 byte)</summary>
	public void Set( params object[] str )
	{
		Clear();
		for( int i=0; i<str.Length; i++ )
			Append( str[ i ] );
	}

	// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
	// Append methods, to build the string without allocation

	///<summary>Reset the m_char array</summary>
	public StringFast Clear()
	{
		m_bufferPos = 0;
		m_isStringGenerated = false;
		return this;
	}

	///<summary>Append a string without memory allocation</summary>
	public StringFast Append( string value )
	{
		ReallocateIFN( value.Length );
		int n = value.Length;
		for( int i=0; i<n; i++ )
			m_buffer[ m_bufferPos + i ] = value[ i ];
		m_bufferPos += n;
		m_isStringGenerated = false;
		return this;
	}
	///<summary>Append an object.ToString(), allocate some memory</summary>
	public StringFast Append( object value )
	{
		Append( value.ToString() );
		return this;
	}

	///<summary>Append an int without memory allocation</summary>
	public StringFast Append( int value )
	{
		// Allocate enough memory to handle any int number
		ReallocateIFN( 16 );

		// Handle the negative case
		if( value < 0 )
		{
			value = -value;
			m_buffer[ m_bufferPos++ ] = '-';
		}

		// Copy the digits in reverse order
		int nbChars = 0;
		do
		{
			m_buffer[ m_bufferPos++ ] = (char)('0' + value%10);
			value /= 10;
			nbChars++;
		} while( value != 0 );

		// Reverse the result
		for( int i=nbChars/2-1; i>=0; i-- )
		{
			char c = m_buffer[ m_bufferPos-i-1 ];
			m_buffer[ m_bufferPos-i-1 ] = m_buffer[ m_bufferPos-nbChars+i ];
			m_buffer[ m_bufferPos-nbChars+i ] = c;
		}
		m_isStringGenerated = false;
		return this;
	}

	///<summary>Append a float without memory allocation.</summary>
	public StringFast Append( float valueF )
	{
		double value = valueF;
		m_isStringGenerated = false;
		ReallocateIFN( 32 ); // Check we have enough buffer allocated to handle any float number

		// Handle the 0 case
		if( value == 0 )
		{
			m_buffer[ m_bufferPos++ ] = '0';
			return this;
		}

		// Handle the negative case
		if( value < 0 )
		{
			value = -value;
			m_buffer[ m_bufferPos++ ] = '-';
		}

		// Get the 7 meaningful digits as a long
		int nbDecimals = 0;
		while( value < 1000000 )
		{
			value *= 10;
			nbDecimals++;
		}
		long valueLong = (long)System.Math.Round( value );

		// Parse the number in reverse order
		int nbChars = 0;
		bool isLeadingZero = true;
		while( valueLong != 0 || nbDecimals >= 0 )
		{
			// We stop removing leading 0 when non-0 or decimal digit
			if( valueLong%10 != 0 || nbDecimals <= 0 )
				isLeadingZero = false;

			// Write the last digit (unless a leading zero)
			if( !isLeadingZero )
				m_buffer[ m_bufferPos + (nbChars++) ] = (char)('0' + valueLong%10);

			// Add the decimal point
			if( --nbDecimals == 0 && !isLeadingZero )
				m_buffer[ m_bufferPos + (nbChars++) ] = '.';

			valueLong /= 10;
		}
		m_bufferPos += nbChars;

		// Reverse the result
		for( int i=nbChars/2-1; i>=0; i-- )
		{
			char c = m_buffer[ m_bufferPos-i-1 ];
			m_buffer[ m_bufferPos-i-1 ] = m_buffer[ m_bufferPos-nbChars+i ];
			m_buffer[ m_bufferPos-nbChars+i ] = c;
		}

		return this;
	}

	// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

	///<summary>Replace all occurences of a string by another one</summary>
	public StringFast Replace( string oldStr, string newStr )
	{
		if( m_bufferPos == 0 )
			return this;

		if( m_replacement == null )
			m_replacement = new List<char>();

		// Create the new string into m_replacement
		for( int i=0; i<m_bufferPos; i++ )
		{
			bool isToReplace = false;
			if( m_buffer[ i ] == oldStr[ 0 ] ) // If first character found, check for the rest of the string to replace
			{
				int k=1;
				while( k < oldStr.Length && m_buffer[ i+k ] == oldStr[ k ] )
					k++;
				isToReplace = (k >= oldStr.Length);
			}
			if( isToReplace ) // Do the replacement
			{
				i += oldStr.Length-1;
				if( newStr != null )
					for( int k=0; k<newStr.Length; k++ )
						m_replacement.Add( newStr[ k ] );
			}
			else // No replacement, copy the old character
				m_replacement.Add( m_buffer[ i ] );
		}

		// Copy back the new string into m_chars
		ReallocateIFN( m_replacement.Count - m_bufferPos );
		for( int k=0; k<m_replacement.Count; k++ )
			m_buffer[ k ] = m_replacement[ k ];
		m_bufferPos = m_replacement.Count;
		m_replacement.Clear();
		m_isStringGenerated = false;
		return this;
	}

	// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

	private void ReallocateIFN( int nbCharsToAdd )
	{
		if( m_bufferPos + nbCharsToAdd > m_charsCapacity )
		{
			m_charsCapacity = System.Math.Max( m_charsCapacity + nbCharsToAdd, m_charsCapacity * 2 );
			char[] newChars = new char[ m_charsCapacity ];
			m_buffer.CopyTo( newChars, 0 );
			m_buffer = newChars;
		}
	}

}